- Published on
Spring AOP源码解析
Spring AOP 源码解析
1. AOP 基础概念
1.1 什么是 AOP
Aspect Oriented Programming(面向切面编程)是一种编程范式,用于处理横切关注点(cross-cutting concerns)。在传统的面向对象编程(OOP)中,业务逻辑通常是自上而下执行的。以登录功能为例:
- 浏览器发起 HTTP 请求到 Controller
- Controller 接收请求,封装参数,进行验证
- 数据传递给 Service 层处理
- Service 调用 DAO 层
- DAO 操作数据库并返回结果
在这个过程中,会产生许多横切性问题,例如:
- Controller 层的日志记录
- Service 层的权限校验
- DAO 层的事务处理
这些横切性问题与主业务逻辑无关,比如日志记录失败不会影响登录功能。如果不处理这些横切性问题,它们会散落在代码的各个角落。AOP 就是将这些横切性问题抽象出来,让程序员能够专注于处理这些横切关注点。
1.2 Spring AOP 简介
AOP 是一种编程目标,而实现这个目标的技术手段有多种:
- JDK 动态代理
- AspectJ 静态代理
- Spring AOP
Spring AOP 是众多 AOP 实现方案中的一种,它基于动态代理技术实现。Spring AOP 的核心技术包括:
- JDK 动态代理
- CGLIB 动态代理
- Spring 内部的 BeanPostProcessor
2. Spring AOP 核心概念
2.1 专业术语
Join Point(连接点)
- 程序 执行过程中的一个点
- Spring AOP 中最小连接单位是方法
- 一个连接点就是一个被连接的方法
Pointcut(切点)
- 一组连接点的集合
- 根据业务和功能对连接点进行分组
- 相当于数据库中的表(连接点相当于表中的数据)
Advice(通知)
通知可以从三个维度理解:
- 通知内容:增强方法的具体业务逻辑(如日志记录、事务操作)
- 通知时机:
- Before advice(前置通知)
- After returning advice(返回通知)
- After throwing advice(异常通知)
- After (finally) advice(最终通知)
- Around advice(环绕通知)
- 通知目标:通知需要作用到的连接点
Introduction(引入)
- 在不修改代码的情况下让类实现某个接口
- 可以指定接口的默认实现方法
Target Object(目标对象)
- 被代理的对象
- 在 Spring AOP 中,通过 CGLIB 或 JDK 动态代理实现增强
AOP Proxy(代理对象)
- 被 JDK 动态代理或 CGLIB 动态代理后的对象
Weaving(织入)
- 将切面和应用 程序对象连接的过程
Aspect(切面)
- 包含切点、连接点、通知等概念的类
- 是 AOP 编程中的核心组件
3. Spring AOP 开发指南
3.1 启用 @AspectJ 支持
Java 配置方式
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("jinggo.spring.example.aop")
public class App {
}
XML 配置方式
<aop:aspectj-autoproxy/>
3.2 声明切面
@Component
@Aspect
public class UserAspect {
}
3.3 声明切点
@Component
@Aspect
public class UserAspect {
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pointCut() {
System.out.println("point cut");
}
}
3.4 声明通知
@Component
@Aspect
public class UserAspect {
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pointCut() {
System.out.println("point cut");
}
@Before("com.yao.aop.UserAspect.pointCut()")
public void beforeAdvice() {
System.out.println("before");
}
}
4. 切点表达式详解
4.1 execution 表达式
最常用的切点表达式,用于匹配方法执行:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
示例:
// 匹配 com.chenss.dao 包下任意接口和类的任意方法
@Pointcut("execution(* com.chenss.dao.*.*(..))")
// 匹配 com.chenss.dao 包下的任意接口和类的 public 方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")
// 匹配 com.chenss.dao 包下的任意接口和类的 public 无参数方法
@Pointcut("execution(public * com.chenss.dao.*.*())")
4.2 within 表达式
用于匹配特定类型中的连接点,粒度比 execution 更大:
// 匹配 com.chenss.dao 包中的任意方 法
@Pointcut("within(com.chenss.dao.*)")
// 匹配 com.chenss.dao 包及其子包中的任意方法
@Pointcut("within(com.chenss.dao..*)")
4.3 args 表达式
用于匹配指定参数类型和数量的方法:
// 匹配第一个参数为 String 类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")
4.4 target 和 this 表达式
- target:指向接口和子类
- this:JDK 代理时指向接口和代理类,CGLIB 代理时指向接口和子类
// 目标对象为 IndexDaoImpl 类
@Pointcut("target(com.chenss.dao.IndexDaoImpl)")
// 当前代理对象
@Pointcut("this(com.chenss.dao.IndexDaoImpl)")
5. 最佳实践
- 优先使用 execution 表达式,它提供了最细粒度的控制
- 合理组织切面,将相关的横切关注点放在同一个切面中
- 使用有意义的切点名称,提高代码可读性
- 注意通知的执行顺序,避免复杂的依赖关系
- 合理使用环绕通知,它提供了最大的灵活性
Spring 事务详解
本文档主要介绍 Spring 框架中事务管理的两种主要方式:编程式事务和声明式事务,以及事务的传播机制。
编程式事务
通过 PlatformTransactionManager
或者 TransactionTemplate
可以实现编程式事务。
PlatformTransactionManager
关键代码
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setName("name"); // 设置事务名称
TransactionStatus transaction = transactionManager.getTransaction(definition);
try {
// 你的JDBC操作
jdbcTemplate.update("update t set v=? where k=?", "n", "k2");
// 提交
transactionManager.commit(transaction);
} catch (Exception e) {
e.printStackTrace();
// 回滚
transactionManager.rollback(transaction);
}
TransactionTemplate
关键代码
// 如果有返回值可以 new TransactionCallback
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("update t set v=? where k=?", "n", "k2");
// 不需要手动提交
} catch (Exception e) {
e.printStackTrace();
// 但是需要手动回滚
status.setRollbackOnly();
}
}
});
声明式事务
XML 配置声明式事务
XML 配置 (beans.xml
)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="xmlUserService" class="com.test.transaction.service.XMLUserService">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/shadow?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="update"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="p" expression="execution(* com.test.transaction.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>
</aop:config>
</beans>
Java 代码 (XMLUserService.java
)
public class XMLUserService {
JdbcTemplate jdbcTemplate;
// 通过xml配置注入jdbcTemplate 纯xml
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void update() {
jdbcTemplate.update("update t set v=? where k=?", "n", "k1");
throw new NullPointerException();
}
}
注解配置声明式事务
配置类 (App.java
)
@Configuration
@EnableTransactionManagement
@ComponentScan("com.test.transaction")
public class App {
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/shadow?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
dataSource.setUsername("root");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
}
如果采用注解,需要在方法上面加 @Transactional
。
混合方式
这种方式没有固定标准,取决于个人喜好,可以选择哪些配置写入 XML,哪些使用 Java 代码。 需要说明的是,混合方式开启事务支撑可以在 XML 中配置,也可以在配置类中进行。
传播机制
Spring 中事务的传播行为定义在 Propagation
枚举中:
public enum Propagation {
// 如果当前存在事务则加入该事务, 如果当前没有事务,则创建一个新的事务
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
// 如果当前存在事务则加入该事务, 如果当前没有事务则以非事务的方式继续运行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
// 如果当前存在事务则加入该事务, 如果当前没有事务则抛出异常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
// 创建一个新的事务, 如果当前存在事务则把当前事务挂起
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
// 以非事务方式运行, 如果当前存在事务则把当前事务挂起
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
// 以非事务方式运行, 如果当前存在事务则抛出异常
NEVER(TransactionDefinition.PROPAGATION_NEVER),
// 如果当前存在事 务则创建一个事务作为当前事务的嵌套事务来运行, 如果不存在则创建一个新的事务
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
如何配置传播机制
编程式事务如何配置
TransactionTemplate
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
PlatformTransactionManager
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
声明式事务配置传播机制和回滚规则
注解方式:
@Transactional(noRollbackFor = IOException.class, propagation = Propagation.REQUIRED)
XML 方式:
<tx:method name="update" propagation="REQUIRED"/>
文中提及的 RuntimeException
, ErrorExp
, IOExp
为事务回滚规则中可能涉及的异常类型示例。